#include "AreaLight.h"
#include "Scene.h"



//////////////////////////////////////////////////
//
//	Sphere Light
//
///////////////////////////////////////////////////

SphereLight::SphereLight(void)
{
	m_material = 0;
	m_area = 1; 
}

SphereLight::~SphereLight(void)
{
	if (m_material) delete m_material;
}

void SphereLight::preCalc()
{
	Sphere::preCalc();

	m_area = 4*PI*m_radius*m_radius;
	LuminusMaterius *material = new LuminusMaterius(m_color, m_wattage/m_area);
	m_material = material;
}

Vector3 SphereLight::sampleLight(const HitInfo &hit, const Scene& scene)
{
	// compute a basis for the disc
	Vector3 rayToCenter = m_center - hit.P;
	rayToCenter.normalize();

	Vector3 tanRay1;
	if(fabs(rayToCenter.x) > fabs(rayToCenter.y))
		tanRay1 = Vector3(0, 1, 0);
	else
		tanRay1 = Vector3(1, 0, 0);
	tanRay1 = cross(rayToCenter, tanRay1);
	tanRay1.normalize();

	Vector3 tanRay2 = cross(rayToCenter, tanRay1);

	// cast several random rays to the light
	Ray randRay;
	float x,y,falloff,dist, nDotL;
	Vector3 discLoc, randDir, result = Vector3(0);
	HitInfo lightHit, shadowHit;
	falloff = (m_center - hit.P).length2();
	for (int i=0; i<NUM_LIGHT_SAMPLES; ++i)
	{
		// compute a random direction
		do {
			x = 1 - 2.0f*((float) rand())/RAND_MAX;
			y = 1 - 2.0f*((float) rand())/RAND_MAX;
		}while(x*x + y*y > 1);
		
		discLoc = x*tanRay1*m_radius + y*tanRay2*m_radius + m_center;
		randDir = (discLoc - hit.P).normalized();
		randRay = Ray(hit.P, randDir);
		this->intersect(lightHit, randRay, epsilon);
		dist = lightHit.t;
		
		//Check if there should be a shadow
		// if any object intersects my light ray closer than the light
		if (!scene.trace(shadowHit, randRay, epsilon, dist-epsilon))
		{        
			// get the diffuse component
			nDotL = dot(hit.N, randRay.d);
			result += std::max(0.0f, nDotL * m_wattage / (4*PI*falloff)) * m_color;
		}
	}

	if (NUM_LIGHT_SAMPLES)
		return result / NUM_LIGHT_SAMPLES;

	return 0;
}



//////////////////////////////////////////////////
//
//	Square Light
//
///////////////////////////////////////////////////

SquareLight::SquareLight(void)
{
	m_material = 0;
	m_area = 1; 
}

SquareLight::~SquareLight(void)
{
	if (m_material) delete m_material;
}

void SquareLight::preCalc()
{
	AASquare::preCalc();

	m_area = m_width*m_width;
	LuminusMaterius *material = new LuminusMaterius(m_color, m_wattage/m_area);
	m_material = material;
}

Vector3 SquareLight::sampleLight(const HitInfo &hit, const Scene& scene)
{
	// cast several random rays to the light
	Ray randRay;
	float x,z,falloff,dist, nDotL;
	Vector3 randPos, randDir, result = Vector3(0);
	HitInfo lightHit, shadowHit;
	falloff = (m_center - hit.P).length2();
	for (int i=0; i<NUM_LIGHT_SAMPLES; ++i)
	{
		// compute a random direction
		x = 1 - 2.0f*((float) rand())/RAND_MAX;
		z = 1 - 2.0f*((float) rand())/RAND_MAX;
		
		randPos = m_center + Vector3(x,0,z);
		randDir = randPos - hit.P;
		falloff = randDir.length2();
		dist = randDir.length();
		randRay = Ray(hit.P, randDir.normalize());
		
		//Check if there should be a shadow
		// if any object intersects my light ray closer than the light
		if (!scene.trace(shadowHit, randRay, epsilon, dist-epsilon))
		{
			// get the diffuse component
			nDotL = dot(hit.N, randRay.d);
			result += std::max(0.0f, nDotL * m_wattage / (4*PI*falloff)) * m_color;
		}
	}

	if (NUM_LIGHT_SAMPLES)
		return result / NUM_LIGHT_SAMPLES;

	return 0;
}



//////////////////////////////////////////////////
//
//	Rect Light
//
///////////////////////////////////////////////////

RectLight::RectLight(void)
{
	m_material = 0;
	m_area = 1;
}

RectLight::~RectLight(void)
{
	if (m_material) delete m_material;
}

void RectLight::preCalc()
{
	AARect::preCalc();
	int u = (m_axis + 1) % 3;
	int v = (m_axis + 2) % 3;

	m_area = fabs(m_c2[u] - m_c1[u]) * fabs(m_c2[v] - m_c1[v]);

	LuminusMaterius *material = new LuminusMaterius(m_color, m_wattage/m_area/PI);
	m_material = material;
}

Vector3 RectLight::sampleLight(const HitInfo &hit, const Scene& scene)
{
	// cast several random rays to the light
	Ray randRay;
	float x,z,w,h,falloff,dist, nDotL;
	Vector3 randPos, randDir, result = Vector3(0);
	HitInfo lightHit, shadowHit;
	int u = (m_axis + 1) % 3;
	int v = (m_axis + 2) % 3;
	w = fabs(m_c2[v] - m_c1[v]);
	h = fabs(m_c2[u] - m_c1[u]);

	for (int i=0; i<NUM_LIGHT_SAMPLES; ++i)
	{
		// compute a random direction
		x = w*(1 - 2.0f*((float) rand())/RAND_MAX);
		z = h*(1 - 2.0f*((float) rand())/RAND_MAX);

		randPos = Vector3(x,0,z);
		randDir = randPos - hit.P;
		falloff = randDir.length2();
		dist = randDir.length();
		randRay = Ray(hit.P, randDir.normalize());

		//Check if there should be a shadow
		// if any object intersects my light ray closer than the light
		if (!scene.trace(shadowHit, randRay, epsilon, dist-epsilon))
		{
			// get the diffuse component
			nDotL = dot(hit.N, randRay.d);
			result += std::max(0.0f, nDotL * m_wattage / (4*PI*falloff)) * m_color;
		}
	}

	if (NUM_LIGHT_SAMPLES)
		return result / NUM_LIGHT_SAMPLES;

	return 0;
}

void RectLight::tracePhoton(LightPath &lp, const Scene &scene)
{
	int u = (m_axis + 1) % 3;
	int v = (m_axis + 2) % 3;
	float w = fabs(m_c2[v] - m_c1[v]);
	float h = fabs(m_c2[u] - m_c1[u]);

	// compute a random position
	Vector3 randPos;
	randPos[m_axis] = m_c2[m_axis];
	randPos[v] = w*(0.5f - lp.Ux);
	randPos[u] = h*(0.5f - lp.Uy);

	float u1, u2;
	lp.getDirVars(0, u1, u2);
	Vector3 randDir = Rand::getRandDir(m_norm, u1, u2);
	Ray randRay = Ray(randPos, randDir.normalize());

	HitInfo hit;
	if (scene.trace(hit, randRay, 0.5))
	{
		hit.material->handlePhoton(randRay, hit, 1, m_color * m_wattage, scene, APPM_PHOTON, &lp);
	}

}
